package research;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import org.apache.commons.lang.StringUtils;

import research.BipartiteGraph.VERTEX_TYPE;

import com.aliasi.spell.JaroWinklerDistance;
import com.csvreader.CsvReader;

import edu.smu.tspell.wordnet.Synset;
import edu.smu.tspell.wordnet.WordNetDatabase;

public class Model {
	private static List<Node> nodes = new ArrayList<Node>();
	private static DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

	public void readCSV(){
		try{
			//CsvReader reader = new CsvReader("E:/Documents/SUNY SB/ACADS/Data Mining/DataSets/data/train_small1.csv");
			CsvReader reader = new CsvReader("/media/EA4A364A4A361433/Documents/SUNY SB/ACADS/Data Mining/DataSets/data/train_big1.csv");
			reader.readHeaders();
			String userId,sku,categoryId,query,clickTime,queryTime;
			while (reader.readRecord()) {
				if(StringUtils.isNotEmpty(userId = reader.get("user"))&&
						StringUtils.isNotEmpty(sku = reader.get("sku"))&&
						StringUtils.isNotEmpty(categoryId = reader.get("category"))&&
						StringUtils.isNotEmpty(query = reader.get("query"))&&
						StringUtils.isNotEmpty(clickTime = reader.get("click_time"))&&
						StringUtils.isNotEmpty(queryTime = reader.get("query_time"))){
					Node node= new Node(userId, sku, categoryId, query, df.parse(clickTime), df.parse(queryTime));
					nodes.add(node);
				}
			}
		}catch(Exception e){
			e.printStackTrace();
		}
	}

	public BipartiteGraph generateBipartiteGraph(){
		BipartiteGraph bipGraph = null;
		try{
			Set<String> queryNodes = new HashSet<String>();
			Set<String> clickNodes = new HashSet<String>();			
			for(Node node:nodes){
				queryNodes.add(node.getQuery());
				clickNodes.add(node.getSku());
			}

			List<Vertex> qVertices = new ArrayList<Vertex>();
			for(String queryNode:queryNodes){
				Vertex v = new Vertex(queryNode);
				qVertices.add(v);
			}

			List<Vertex> cVertices = new ArrayList<Vertex>();
			for(String clickNode:clickNodes){
				Vertex v = new Vertex(clickNode);
				cVertices.add(v);
			}			

			List<Edge> forwardEdges = new ArrayList<Edge>();
			List<Edge> backwardEdges = new ArrayList<Edge>();

			System.out.println("# qVertices = "+qVertices.size());
			System.out.println("# cVertices = "+cVertices.size());
			int count = 0;
			for(Vertex qVertex:qVertices){
				for(Vertex cVertex:cVertices){
					for(Node node:nodes){
						if(node.getQuery().equals(qVertex.toString()) && node.getSku().equals(cVertex.toString())){
							cVertex.setTs(node.getClickTime().getTime()-node.getQueryTime().getTime());
							Edge forwardEdge = new Edge(qVertex, cVertex, node.getClickTime().getTime()-node.getQueryTime().getTime());
							if(!forwardEdges.contains(forwardEdge)){
								forwardEdges.add(forwardEdge);
							}

							Edge backwardEdge = new Edge(cVertex, qVertex, node.getClickTime().getTime()-node.getQueryTime().getTime());
							if(!backwardEdges.contains(backwardEdge)){
								backwardEdges.add(backwardEdge);
							}
						}
					}
				}
				System.out.println(++count);
			}

			bipGraph = new BipartiteGraph(qVertices, cVertices, forwardEdges, backwardEdges); //backward edges - taken care of

		}catch(Exception e){
			e.printStackTrace();
		}

		return bipGraph;
	}


	public BipartiteGraph agglomerativeClustering(BipartiteGraph bGraph){
		try{
			while(true){
				//score all pairs of query nodes
				List<Float> similarityScores = new ArrayList<Float>();
				Float similarity;
				List<NodeSimilarity> nodeSimilarities = new ArrayList<NodeSimilarity>();

				List<Vertex> qNodes = bGraph.getQueryVertices();
				for(Vertex qNodeA:qNodes){
					for(Vertex qNodeB:qNodes){
						if(qNodeA.equals(qNodeB)){
							continue;
						}else{
							similarity = similarity(qNodeA, qNodeB, bGraph.getForwardEdges());
							similarityScores.add(similarity);
							NodeSimilarity ns = new NodeSimilarity(qNodeA, qNodeB, similarity);
							nodeSimilarities.add(ns);
						}
					}
				}

				//merge maximum similar query nodes
				Collections.sort(similarityScores, Collections.reverseOrder());
				Float maxQueryNodeSimilarity = similarityScores.get(0);
				if(maxQueryNodeSimilarity!=0){
					bGraph = mergeMaxSimilarNodes(nodeSimilarities,maxQueryNodeSimilarity,bGraph,bGraph.getQueryVertices(), bGraph.getForwardEdges(),BipartiteGraph.VERTEX_TYPE.Q_VERTEX);
				}

				//score all pairs of click nodes - //backward edges - taken care of
				List<Vertex> cNodes = bGraph.getClickVertices();
				similarityScores = new ArrayList<Float>();
				nodeSimilarities = new ArrayList<NodeSimilarity>();
				for(Vertex cNodeA:cNodes){
					for(Vertex cNodeB:cNodes){
						if(cNodeA.equals(cNodeB)){
							continue;
						}else{
							similarity = similarity(cNodeA, cNodeB, bGraph.getBackwardEdges());
							similarityScores.add(similarity);
							NodeSimilarity ns = new NodeSimilarity(cNodeA, cNodeB, similarity);
							nodeSimilarities.add(ns);
						}
					}
				}

				//merge maximum similar click nodes
				Collections.sort(similarityScores, Collections.reverseOrder());
				Float maxClickNodeSimilarity = similarityScores.get(0);
				if(maxClickNodeSimilarity!=0){
					bGraph = mergeMaxSimilarNodes(nodeSimilarities,maxClickNodeSimilarity,bGraph,bGraph.getClickVertices(), bGraph.getBackwardEdges(),BipartiteGraph.VERTEX_TYPE.C_VERTEX);
				}

				if(maxQueryNodeSimilarity==0 && maxClickNodeSimilarity==0){
					break;
				}

			}


		}catch(Exception e){
			e.printStackTrace();
		}

		return bGraph;
	}

	private BipartiteGraph mergeMaxSimilarNodes(
			List<NodeSimilarity> nodeSimilarities, Float maxSimilarity,
			BipartiteGraph bGraph, List<Vertex> vertexList, List<Edge> edges, VERTEX_TYPE vertexType ) {
		try{
			for(NodeSimilarity ns:nodeSimilarities){
				if(ns.getSimilarity() == maxSimilarity){
					//1. In bGraph add nodeB to nodeA's similarVertices
					//2. remove nodeB from bGraph, but before that add nodeB's neighbours to that of nodeA, if they
					//are not already
					//3. Update bGraph

					//In bGraph add nodeB to nodeA's similarVertices
					List<Vertex> nodeAsimVertices = ns.getNodeA().getSimilarVertices();
					if(nodeAsimVertices==null){
						nodeAsimVertices = new ArrayList<Vertex>();
					}
					nodeAsimVertices.add(ns.getNodeB());
					ns.getNodeA().setSimilarVertices(nodeAsimVertices);

					//remove nodeB from bGraph, but before that add nodeB's neighbours to that of nodeA, if they
					//are not already
					Set<Vertex> nodeBneighbours = getNeighbours(ns.getNodeB(), edges);
					Set<Vertex> nodeAneighbours = getNeighbours(ns.getNodeA(), edges);

					for(Vertex nodeBneighbour:nodeBneighbours){
						if(!nodeAneighbours.contains(nodeBneighbour)){
							Edge oldEdge = bGraph.getEdge(ns.getNodeB(), nodeBneighbour, edges);
							Edge newNodeAForwardEdge = new Edge(ns.getNodeA(), nodeBneighbour, oldEdge.getWeight());
							Edge newNodeABackwardEdge = new Edge(nodeBneighbour, ns.getNodeA(), oldEdge.getWeight());

							if(vertexType==BipartiteGraph.VERTEX_TYPE.Q_VERTEX){
								if(!edges.contains(newNodeAForwardEdge)){
									edges.add(newNodeAForwardEdge);
									bGraph.setForwardEdges(edges);
								}
								List<Edge> backwardEdges = bGraph.getBackwardEdges();
								if(!backwardEdges.contains(newNodeABackwardEdge)){
									backwardEdges.add(newNodeABackwardEdge);
									bGraph.setBackwardEdges(backwardEdges);
								}
							}
							else if(vertexType==BipartiteGraph.VERTEX_TYPE.C_VERTEX){
								if(!edges.contains(newNodeAForwardEdge)){
									edges.add(newNodeAForwardEdge);
									bGraph.setBackwardEdges(edges);
								}
								List<Edge> forwardEdges = bGraph.getForwardEdges();
								if(!forwardEdges.contains(newNodeABackwardEdge)){
									forwardEdges.add(newNodeABackwardEdge);
									bGraph.setForwardEdges(forwardEdges);
								}
							}
						}
						Edge nodeBedgeToBeRemoved = bGraph.getEdge(ns.getNodeB(), nodeBneighbour, edges);
						edges.remove(nodeBedgeToBeRemoved);

						if(vertexType==BipartiteGraph.VERTEX_TYPE.Q_VERTEX){
							Edge nodeBbackEdgeToBeRemoved = bGraph.getEdge(nodeBneighbour, ns.getNodeB(),edges);
							List<Edge> backwardEdges = bGraph.getBackwardEdges();
							if(backwardEdges.contains(nodeBbackEdgeToBeRemoved)){
								backwardEdges.remove(nodeBbackEdgeToBeRemoved);
								bGraph.setBackwardEdges(backwardEdges);
							}
						}
						else if(vertexType==BipartiteGraph.VERTEX_TYPE.C_VERTEX){
							Edge nodeBforwardEdgeToBeRemoved = bGraph.getEdge(nodeBneighbour, ns.getNodeB(),edges);
							List<Edge> forwardEdges = bGraph.getForwardEdges();
							if(forwardEdges.contains(nodeBforwardEdgeToBeRemoved)){
								forwardEdges.remove(nodeBforwardEdgeToBeRemoved);
								bGraph.setForwardEdges(forwardEdges);
							}
						}
					}
					if(vertexList.contains(ns.getNodeB())){
						vertexList.remove(ns.getNodeB());
						if(vertexType==BipartiteGraph.VERTEX_TYPE.Q_VERTEX)
							bGraph.setQueryVertices(vertexList);
						else
							bGraph.setClickVertices(vertexList);
					}
				}
			}

		}catch(Exception e){
			e.printStackTrace();
		}

		return bGraph;
	}


	public Set<Vertex> getNeighbours(Vertex v, List<Edge> edges){
		Set<Vertex> vNeighbours = new TreeSet<Vertex>();

		try{
			for(Edge edge:edges){
				if(edge.getSource().equals(v) && edge.getDestination()!=null){
					vNeighbours.add(edge.getDestination());
				}
			}

		}catch(Exception e){
			e.printStackTrace();
		}

		return vNeighbours;
	}

	public float similarity(Vertex vA, Vertex vB, List<Edge> edges){
		float similarity = 0;
		Set<Vertex> vANeighbours = null, vBNeighbours = null;

		try{
			vANeighbours = getNeighbours(vA, edges);
			vBNeighbours = getNeighbours(vB, edges);

			Set<Vertex> denominator = union(vANeighbours, vBNeighbours);
			Set<Vertex> numerator = intersection(vANeighbours, vBNeighbours);

			if(denominator.size()>0){
				similarity = (numerator.size()*1.0f)/denominator.size();
			}

		}catch(Exception e){
			e.printStackTrace();
		}

		return similarity;
	}

	public static <T> Set<T> union(Set<T> setA, Set<T> setB) {
		Set<T> tmp = new TreeSet<T>(setA);
		tmp.addAll(setB);
		return tmp;
	}

	public static <T> Set<T> intersection(Set<T> setA, Set<T> setB) {
		Set<T> tmp = new TreeSet<T>();
		for (T x : setA)
			if (setB.contains(x))
				tmp.add(x);
		return tmp;
	}

	public static Set<String> intersectionEx(Set<String> setA, Set<String> setB) {
		JaroWinklerDistance jaroWinkler = JaroWinklerDistance.JARO_WINKLER_DISTANCE;
		Set<String> tmp = new TreeSet<String>();
		for (String x : setA)
			for(String y : setB){
				if(jaroWinkler.proximity(x.toLowerCase(),y.toLowerCase())>=0.85){
					tmp.add(x);
				}
			}
		return tmp;
	}

	public static Set<String> getSimilarWords(String word){
		Set<String> similarWords = new HashSet<String>();
		try{
			//  Concatenate the command-line arguments
			String wordForm = word;

			//  Get the synsets containing the word form
			WordNetDatabase database = WordNetDatabase.getFileInstance();
			Synset[] synsets = database.getSynsets(wordForm);

			//  Display the word forms and definitions for synsets retrieved
			if (synsets.length > 0)
			{
				for (int i = 0; i < synsets.length; i++)
				{
					String[] wordForms = synsets[i].getWordForms();
					for (int j = 0; j < wordForms.length; j++)
					{
						if(wordForms[j].split(" ").length == 1)
							similarWords.add(wordForms[j]);
					}
				}
			}
		}catch(Exception e){
			e.printStackTrace();
		}

		return similarWords;
	}

	public static float computerTermVectorSimilarity(Set<String> qSet,
			Set<String> rSet) {
		float termVectorSimilarity = 0;

		try{
			Set<String> denominator = union(qSet, rSet);
			Set<String> numerator = intersectionEx(qSet, rSet);

			if(denominator.size()>0){
				termVectorSimilarity = (numerator.size()*1.0f)/denominator.size();
			}

		}catch(Exception e){
			e.printStackTrace();
		}

		return termVectorSimilarity;
	}

}
